#!/bin/bash

# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

SCRIPT="$(readlink -f "$0")"
SCRIPT_DIR="$(dirname "$SCRIPT")"

EC_DIR="$(readlink -f "${SCRIPT_DIR}/..")"
if [[ "$(basename "${EC_DIR}")" != "ec" ]]; then
	EC_DIR=
fi

# Loads script libraries.
. "/usr/share/misc/shflags" || exit 1

# Redirects tput to stderr, and drop any error messages.
tput2() {
	tput "$@" 1>&2 2>/dev/null || true
}

error() {
	tput2 bold && tput2 setaf 1
	echo "ERROR: $*" >&2
	tput2 sgr0
}


info() {
	tput2 bold && tput2 setaf 2
	echo "INFO: $*" >&2
	tput2 sgr0
}

warn() {
	tput2 bold && tput2 setaf 3
	echo "WARNING: $*" >&2
	tput2 sgr0
}

die() {
	[ -z "$*" ] || error "$@"
	exit 1
}

# Note: Link is a special case and is not included here.
BOARDS_LM4=(
	falco
	peppy
	rambi
	samus
	squawks
)

BOARDS_STM32=(
	big
	blaze
	discovery
	firefly
	fruitpie
	nyan
	pit
	plankton
	ryu
	ryu_sh
	samus_pd
	snow
	spring
	veyron
	zinger
)

BOARDS_STM32_DFU=(
	twinkie
)

# Flags
DEFINE_string board "${DEFAULT_BOARD}" \
	"The board to run debugger on."
DEFINE_string image "" \
	"Full pathname of the EC firmware image to flash."
DEFINE_string offset "0" \
	"Offset where to program the image from."
DEFINE_integer port 9999 \
	"Port to communicate to servo on."
DEFINE_boolean ro "${FLAGS_FALSE}" \
	"Write only the read-only partition"
DEFINE_boolean unprotect "${FLAGS_FALSE}" \
	"Clear the protect flag."

# Parse command line
FLAGS_HELP="usage: $0 [flags]"
FLAGS "$@" || exit 1
eval set -- "${FLAGS_ARGV}"
if [[ $# -gt 0 ]]; then
	die "invalid arguments: \"$*\""
fi

set -e

SERVO_TYPE=servo

in_array() {
	local n=$#
	local value=${!n}

	for (( i=1; i<$#; i++ )) do
		if [ "${!i}" == "${value}" ]; then
			return 0
		fi
	done
	return 1
}

# reset the EC
toad_ec_hard_reset() {
	if dut_control cold_reset 2>/dev/null ; then
		dut_control cold_reset:on
		dut_control cold_reset:off
	else
		info "you probably need to hard-reset your EC manually"
	fi
}

servo_ec_hard_reset() {
	dut_control cold_reset:on
	dut_control cold_reset:off
}

servo_usbpd_hard_reset() {
	dut_control usbpd_reset:on sleep:0.5 usbpd_reset:off
}

servo_sh_hard_reset() {
	dut_control sh_reset:on
	dut_control sh_reset:off
}

ec_reset() {
	eval ${SERVO_TYPE}_${MCU}_hard_reset
}

# force the EC to boot in serial monitor mode
toad_ec_boot0() {
	dut_control boot_mode:yes
}

servo_ec_boot0() {
	dut_control spi1_vref:pp3300
}

servo_usbpd_boot0() {
	dut_control usbpd_boot_mode:on
}

servo_sh_boot0() {
	dut_control sh_boot_mode:on
}

ec_enable_boot0() {
	eval ${SERVO_TYPE}_${MCU}_boot0
}

# Put back the servo and the system in a clean state at exit
cleanup() {
	if [ -n "${save}" ]; then
		info "Restoring servo settings..."
		servo_restore "$save"
	fi

	ec_reset
}
trap cleanup EXIT

BOARD=${FLAGS_board}
BOARD_ROOT=/build/${BOARD}

# Possible default EC images
if [ "${FLAGS_ro}" = ${FLAGS_TRUE} ] ; then
	EC_FILE=ec.RO.flat
else
	EC_FILE=ec.bin
fi

EMERGE_BUILD=${BOARD_ROOT}/firmware/${EC_FILE}

LOCAL_BUILD=
if [[ -n "${EC_DIR}" ]]; then
	LOCAL_BUILD="${EC_DIR}/build/${BOARD}/${EC_FILE}"
fi

# Find the EC image to use
function ec_image() {
	# No image specified on the command line, try default ones
	if [[ -n "${FLAGS_image}" ]] ; then
		if [ -f "${FLAGS_image}" ]; then
			echo "${FLAGS_image}"
			return
		fi
		die "Invalid image path : ${FLAGS_image}"
	else
		if [ -f "${LOCAL_BUILD}" ]; then
			echo "${LOCAL_BUILD}"
			return
		fi
		if [ -f "${EMERGE_BUILD}" ]; then
			echo "${EMERGE_BUILD}"
			return
		fi
	fi
	die "no EC image found : build one or specify one."
}

DUT_CONTROL_CMD="dut-control --port=${FLAGS_port}"

# Find the EC UART on the servo v2
function ec_uart() {
	SERVOD_FAIL="Cannot communicate with servo. is servod running ?"
	($DUT_CONTROL_CMD ${MCU}_uart_pty || \
	    die "${SERVOD_FAIL}") | cut -d: -f2
}

# Servo variables management
case "${BOARD}" in
	ryu_sh ) MCU="sh" ;;
	samus_pd ) MCU="usbpd" ;;
	twinkie ) DUT_CONTROL_CMD="true" ; MCU="ec" ;;
	*) MCU="ec" ;;
esac

servo_VARS="${MCU}_uart_en ${MCU}_uart_parity \
${MCU}_uart_baudrate jtag_buf_on_flex_en jtag_buf_en spi1_vref"
if [[ "${MCU}" == "usbpd" ]] ; then
	servo_VARS+=" usbpd_boot_mode"
fi
toad_VARS="${MCU}_uart_parity \
${MCU}_uart_baudrate boot_mode"

function dut_control() {
	$DUT_CONTROL_CMD "$@" >/dev/null
}

function servo_save() {
	SERVO_VARS_NAME=${SERVO_TYPE}_VARS
	$DUT_CONTROL_CMD ${!SERVO_VARS_NAME}
}

function servo_restore() {
	echo "$1" | while read line
	do
		dut_control "$line"
	done
}

function free_pty() {
	local pids

	pids=$(lsof -F p 2>/dev/null -- $1 | cut -d'p' -f2)
	if [ "${pids}" == "" ]; then
		return
	fi

	# Try to kill nicely at first...
	kill ${pids}
	info "You'll need to re-launch console on $1"

	# Wait up to 3 seconds for them to die...
	for i in $(seq 30); do
		pids=$(lsof -F p 2>/dev/null -- $1 | cut -d'p' -f2)
		if [ "${pids}" == "" ]; then
			return
		fi
		sleep .1
	done

	# Forcibly kill
	kill -9 ${pids}
}

# Board specific flashing scripts

function flash_stm32() {
	TOOL_PATH="${EC_DIR}/build/${BOARD}/util:$PATH"
	STM32MON=$(PATH="${TOOL_PATH}" which stm32mon)
	if [ ! -x "$STM32MON" ]; then
		die "no stm32mon util found."
	fi

	if [ "${FLAGS_unprotect}" = ${FLAGS_TRUE} ] ; then
		# Unprotect exists, but isn't needed because erasing pstate is
		# implicit in writing the entire image
		die "--unprotect not supported for this board."
	fi

	info "Using serial flasher : ${STM32MON}"
	free_pty ${EC_UART}

	if [ "${SERVO_TYPE}" = "servo" ] ; then
		dut_control ${MCU}_uart_en:on
	fi
	dut_control ${MCU}_uart_parity:even
	dut_control ${MCU}_uart_baudrate:115200
	# Force the EC to boot in serial monitor mode
	ec_enable_boot0
	# Reset the EC
	ec_reset
	# Unprotect flash, erase, and write
	${STM32MON} -d ${EC_UART} -u -e -w "${IMG}"
}

function flash_stm32_dfu() {
	DFU_DEVICE=0483:df11
	ADDR=0x08000000
	DFU_UTIL='dfu-util'
	which $DFU_UTIL &> /dev/null || die \
		"no dfu-util util found.  Did you 'sudo emerge dfu-util'"

	info "Using dfu flasher : ${DFU_UTIL}"

	dev_cnt=$(lsusb -d $DFU_DEVICE | wc -l)
	if [ $dev_cnt -eq 0 ] ; then
		die "unable to locate dfu device at $DFU_DEVICE"
	elif [ $dev_cnt -ne 1 ] ; then
		die "too many dfu devices (${dev_cnt}). Disconnect all but one."
	fi

	SIZE=$(wc -c ${IMG} | cut -d' ' -f1)
	sudo $DFU_UTIL -a 0 -s ${ADDR}:${SIZE} -D "${IMG}"
}

function flash_link() {
	[[ -n "${EC_DIR}" ]] || die "Cannot locate openocd script"

	OCD_CFG="servo_v2_slower.cfg"
	OCD_PATH="${EC_DIR}/chip/lm4/openocd"
	OCD_CMDS="init; flash_lm4 ${IMG} ${FLAGS_offset};"
	if [ "${FLAGS_unprotect}" = ${FLAGS_TRUE} ] ; then
		info "Clearing write protect flag."
		OCD_CMDS="${OCD_CMDS} unprotect_link;"
	fi
	OCD_CMDS="${OCD_CMDS} shutdown;"

	dut_control jtag_buf_on_flex_en:on
	dut_control jtag_buf_en:on

	sudo openocd -s "${OCD_PATH}" -f "${OCD_CFG}" -c "${OCD_CMDS}" || \
	die "Failed to program ${IMG}"
}

function flash_lm4() {
	[[ -n "${EC_DIR}" ]] || die "Cannot locate openocd script"

	OCD_CFG="servo_v2_slower.cfg"
	OCD_PATH="${EC_DIR}/chip/lm4/openocd"
	OCD_CMDS="init; flash_lm4 ${IMG} ${FLAGS_offset};"
	if [ "${FLAGS_unprotect}" = ${FLAGS_TRUE} ] ; then
		# Unprotect exists, but isn't needed because erasing pstate is
		# implicit in writing the entire image
		die "--unprotect not supported for this board."
	fi
	OCD_CMDS="${OCD_CMDS} shutdown;"

	dut_control jtag_buf_on_flex_en:on
	dut_control jtag_buf_en:on

	sudo openocd -s "${OCD_PATH}" -f "${OCD_CFG}" -c "${OCD_CMDS}" || \
	die "Failed to program ${IMG}"
}

if dut_control boot_mode 2>/dev/null ; then
	if [[ "${MCU}" != "ec" ]] ; then
		die "Toad cable can't support non-ec UARTs"
	fi
	SERVO_TYPE=toad
	info "Using a dedicated debug cable"
fi

IMG="$(ec_image)"
info "Using ${MCU} image : ${IMG}"

EC_UART="$(ec_uart)"
info "${MCU} UART pty : ${EC_UART}"

save="$(servo_save)"

if $(in_array "${BOARDS_LM4[@]}" "${BOARD}"); then
	flash_lm4
elif $(in_array "${BOARDS_STM32[@]}" "${BOARD}"); then
	flash_stm32
elif $(in_array "${BOARDS_STM32_DFU[@]}" "${BOARD}"); then
	flash_stm32_dfu
elif [ "${BOARD}" == "link" ]; then
	flash_link
else
	die "board ${BOARD} not supported"
fi

info "Flashing done."
